from typing import Tuple
import logging

import numpy as np
import pandas as pd

Spot = Tuple[int, int]
Direction = Tuple[float, float]


class SmlGraph(object):
    """相似图"""
    def __init__(self):
        self.__gamma = None
        self.__mat_gist = np.ndarray
        self.__data = pd.DataFrame

    def set_dataframe(self, df: pd.DataFrame):
        """
        光流追踪的特征点数据, 特征点的坐标序列
        Parameters
        ----------
        df : pandas.DataFrame[-1, 4]

        Returns
        -------

        """
        assert df.shape[1] == 4, "the shape must be (-1, 4)"

        if self.__data is not None:
            logging.warning("the data is already exist, it will be overrode")
        self.__data = df.dropna()

    def sml(self, s1: Spot, vector1: Direction, s2: Spot, vector2: Direction) -> float:
        """
        calculate the similarity between s1 and s2

        Parameters
        ----------
        s1 : Tuple[int, int]
            s1 co-coordinate
        vector1 : Tuple[float, float]
            s1 direction vector
        s2 : Tuple[int, int]
            s2 co-coordinate
        vector2 : Tuple[float, float]
            s2 direction vector

        Returns
        -------
        float
            similarity
        """
        if self.__gamma is None:
            self.train()

        if SmlGraph.mod(s1, s2) <= self.__gamma:
            res = np.dot(vector1, vector2) \
                  / (np.linalg.norm(vector1) * np.linalg.norm(vector2))
            return max(res, 0.0)
        else:
            return 0.0

    def train(self):
        _shape = self.__data.shape[0]
        self.__mat_gist = np.zeros((_shape, _shape))
        li_gist = []
        for i in range(_shape):
            for j in range(i):
                _mod = SmlGraph.mod(self.__data.iloc[i, 0:2], self.__data.iloc[j, 0:2])
                self.__mat_gist[i, j] = _mod
                li_gist.append(_mod)
        li_gist.sort()
        self.__gamma = li_gist[_shape - 1]

    @property
    def gamma(self):
        return self.__gamma

    @staticmethod
    def mod(s1: Spot, s2: Spot) -> float:
        return np.linalg.norm(np.array(s1) - np.array(s2))

